using System.Linq.Expressions;
using FastDevTemplate.Common.Util;
using FastDevTemplate.DataAccess;
using FastDevTemplate.DataAccess.Base;
using FastDevTemplate.Service.Interface.Base;
using Microsoft.EntityFrameworkCore;

namespace FastDevTemplate.Service.Base;

public class GuidCurdService<TEntity, TEntityResultDto, TEntityGetDto, TAddEntityDto, TUpdateEntityDto>(
    FastDevTemplateDbContext dbContext) :
    BaseCurdService<TEntity, Guid, TEntityResultDto, TEntityGetDto, TAddEntityDto, TUpdateEntityDto>(dbContext)
    where TEntity : Entity<Guid>, new()
    where TEntityResultDto : class
    where TEntityGetDto : class
    where TAddEntityDto : class
    where TUpdateEntityDto : class
{
    public override Guid GenerateKey(TEntity entity)
    {
        entity.ID = Guid.NewGuid();
        return entity.ID;
    }
}

public abstract class BaseCurdService<TEntity, TKey, TEntityResultDto, TEntityGetDto, TAddEntityDto, TUpdateEntityDto>(
    DbContext dbContext) :
    BaseQueryService<TEntity, TKey, TEntityResultDto, TEntityGetDto>(dbContext),
    IBaseCurdService<TEntity, TKey, TEntityResultDto, TEntityGetDto, TAddEntityDto, TUpdateEntityDto>
    where TEntity : class, new()
    where TEntityResultDto : class
    where TEntityGetDto : class
    where TAddEntityDto : class
    where TUpdateEntityDto : class
{
    public abstract TKey GenerateKey(TEntity entity);


    #region Sync

    public virtual TEntityResultDto? Add(TAddEntityDto commandDto)
    {
        return AddAsync(commandDto).Result;
    }

    public virtual TEntityResultDto? Update(TKey id, TUpdateEntityDto commandDto)
    {
        return ModifyAsync(id, commandDto).Result;
    }

    public virtual TEntityResultDto? Remove(TKey id)
    {
        return RemoveAsync(id).Result;
    }

    #endregion

    #region Async

    public virtual async Task<TEntityResultDto?> AddAsync(TAddEntityDto commandDto)
    {
        var entity = ObjectMapper.Map<TAddEntityDto, TEntity>(commandDto);
        GenerateKey(entity);
        Dao.Add(entity);
        try
        {
            if (await Dao.SaveChangesAsync() <= 0)
            {
                ErrorHandling(ErrorType.DataBaseSaveError, nameof(AddAsync));
                return null;
            }
        }
        catch (Exception e)
        {
            ErrorHandling(ErrorType.DataBaseSaveError, e);
            return null;
        }

        return ObjectMapper.Map<TEntityResultDto>(entity);
    }

    public virtual async Task<TEntityResultDto?> ModifyAsync(TKey id, TUpdateEntityDto commandDto)
    {
        var entity = await DbSet.FindAsync(id);

        if (entity == null)
        {
            ErrorHandling(ErrorType.LogicNotFindEntityError, nameof(ModifyAsync));
            return null;
        }

        ObjectMapper.Map(commandDto, entity);
        Dao.Update(entity);

        try
        {
            if (await Dao.SaveChangesAsync() <= 0)
            {
                ErrorHandling(ErrorType.DataBaseSaveError, nameof(ModifyAsync));
                return null;
            }
        }
        catch (Exception e)
        {
            ErrorHandling(ErrorType.DataBaseSaveError, e);
        }

        return ObjectMapper.Map<TEntityResultDto>(entity);
    }

    public virtual async Task<TEntityResultDto?> RemoveAsync(TKey id)
    {
        var entity = await DbSet.FindAsync(id);
        if (entity == null)
        {
            ErrorHandling(ErrorType.LogicNotFindEntityError);
            return null;
        }

        Dao.Remove(entity);

        try
        {
            if (await Dao.SaveChangesAsync() <= 0)
            {
                ErrorHandling(ErrorType.DataBaseSaveError, nameof(RemoveAsync));
                return null;
            }
        }
        catch (Exception e)
        {
            ErrorHandling(ErrorType.DataBaseSaveError, e);
            return null;
        }

        return ObjectMapper.Map<TEntityResultDto>(entity);
    }

    #endregion
}

public abstract class BaseQueryService<TEntity, TKey, TEntityResultDto, TEntityGetDto>(DbContext dbContext) :
    BaseService,
    IBaseQueryService<TEntity, TKey, TEntityResultDto, TEntityGetDto>
    where TEntity : class, new()
    where TEntityResultDto : class
    where TEntityGetDto : class
{
    protected readonly DbContext Dao = dbContext;
    protected readonly DbSet<TEntity> DbSet = dbContext.Set<TEntity>();

    public virtual IEnumerable<TEntityResultDto> GetSet(
        params Expression<Func<TEntity, bool>>[] filterExpression)
    {
        return GetSetAsync(filterExpression).Result;
    }

    public virtual PageResultDto<TEntityResultDto> GetPage(int pageIndex, int pageSize,
        params Expression<Func<TEntity, bool>>[] filterExpression)
    {
        return GetPageAsync(pageIndex, pageSize, filterExpression).Result;
    }

    public bool Exist(Expression<Func<TEntity, bool>> filterExpression)
    {
        return ExistAsync(filterExpression).Result;
    }

    public virtual TEntityGetDto? GetById(TKey id)
    {
        return GetByIdAsync(id).Result;
    }

    public virtual async Task<IEnumerable<TEntityResultDto>> GetSetAsync(
        params Expression<Func<TEntity, bool>>[] filterExpression)
    {
        var query = DbSet.AsNoTracking().AsQueryable();
        foreach (var filter in filterExpression)
            query = query.Where(filter);
        return await query
            .Select(item => ObjectMapper.Map<TEntityResultDto>(item))
            .ToListAsync();
    }

    public virtual async Task<IEnumerable<TEntityResultDto>> GetSetAsync(
        Func<TEntityResultDto, TEntityResultDto>? dtoForEch = null,
        params Expression<Func<TEntity, bool>>[] filterExpression)
    {
        dtoForEch ??= dto => dto;
        var query = DbSet.AsNoTracking().AsQueryable();
        foreach (var filter in filterExpression)
            query = query.Where(filter);
        return await query
            .Select(item => dtoForEch(ObjectMapper.Map<TEntityResultDto>(item)))
            .ToListAsync();
    }

    public Task<bool> ExistAsync(Expression<Func<TEntity, bool>> filterExpression)
    {
        return DbSet.AsNoTracking().AnyAsync(filterExpression);
    }

    //当前分页方式可能存在性能问题，请查阅微软EFCore文档
    public virtual async Task<PageResultDto<TEntityResultDto>> GetPageAsync(int pageIndex, int pageSize,
        params Expression<Func<TEntity, bool>>[] filterExpression)
    {
        pageIndex = pageIndex <= 1 ? 0 : pageIndex - 1;

        var query = DbSet.AsNoTracking().AsQueryable();

        foreach (var filter in filterExpression)
            query = query.Where(filter);

        var queryCount = await query.CountAsync();

        query = query.Skip(pageIndex * pageSize)
            .Take(pageSize);

        return new PageResultDto<TEntityResultDto>
        {
            QueryData = await query
                .Select(item => ObjectMapper.Map<TEntityResultDto>(item))
                .ToListAsync(),
            PageIndex = pageIndex + 1,
            PageSize = pageSize,
            AllCount = queryCount
        };
    }

    // public virtual async Task<PageResultDto<TEntityResultDto>> GetPageAsync(int pageIndex, int pageSize,
    //     Func<TEntityResultDto, TEntityResultDto>? dtoForEch = null,
    //     params Expression<Func<TEntity, bool>>[] filterExpression)
    // {
    //     pageIndex = pageIndex <= 1 ? 0 : pageIndex - 1;
    //     dtoForEch ??= dto => dto;
    //
    //     var query = DbSet.AsNoTracking().AsQueryable();
    //
    //     foreach (var filter in filterExpression)
    //         query = query.Where(filter);
    //
    //     var queryCount = await query.CountAsync();
    //
    //     query = query.Skip(pageIndex * pageSize)
    //         .Take(pageSize);
    //
    //     return new PageResultDto<TEntityResultDto>
    //     {
    //         QueryData = await query
    //             .Select(item => dtoForEch(ObjectMapper.Map<TEntityResultDto>(item)))
    //             .ToListAsync(),
    //         PageIndex = pageIndex + 1,
    //         PageSize = pageSize,
    //         AllCount = queryCount
    //     };
    // }

    public virtual async Task<PageResultDto<TEntityResultDto>> GetPageAsync<TSortKey>(int pageIndex, int pageSize,
        Expression<Func<TEntity, TSortKey>> orderBy,
        bool isAscendingOrder = true,
        params Expression<Func<TEntity, bool>>[] filterExpression)
    {
        pageIndex = pageIndex <= 1 ? 0 : pageIndex - 1;

        var query =
            (isAscendingOrder ? DbSet.OrderBy(orderBy) : DbSet.OrderByDescending(orderBy))
            .AsQueryable()
            .AsNoTracking();

        foreach (var filter in filterExpression)
            query = query.Where(filter);
        var queryCount = await query.CountAsync();
        query = query
            .Skip(pageIndex * pageSize)
            .Take(pageSize);

        return new PageResultDto<TEntityResultDto>
        {
            QueryData = await query
                .Select(item => ObjectMapper.Map<TEntityResultDto>(item))
                .ToListAsync(),
            PageIndex = pageIndex + 1,
            PageSize = pageSize,
            AllCount = queryCount
        };
    }

    public virtual async Task<TEntityGetDto?> GetByIdAsync(TKey id)
    {
        var entity = await EntityFindAsync(id);
        if (entity == null)
        {
            ErrorHandling(ErrorType.LogicNotFindEntityError, nameof(GetByIdAsync));
            return null;
        }

        return ObjectMapper.Map<TEntityGetDto>(entity);
    }

    protected virtual TEntity? EntityFind(TKey id)
    {
        return EntityFindAsync(id).Result;
    }

    protected virtual ValueTask<TEntity?> EntityFindAsync(TKey id)
    {
        return DbSet.FindAsync(id);
    }

    public virtual IEnumerable<TEntityResultDto> GetSet(
        Func<TEntityResultDto, TEntityResultDto>? dtoForEch = null,
        params Expression<Func<TEntity, bool>>[] filterExpression)
    {
        return GetSetAsync(dtoForEch, filterExpression).Result;
    }
}